﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using Pfz.Extensions;
using Pfz.Threading;
using System.Threading;
using System.Linq;
using Pfz.DynamicObjects.Internal;

namespace Pfz.DynamicObjects.CloneToModifyModel
{
	/// <summary>
	/// Class responsible for generating the run-time implementation of ICtmObjects.
	/// </summary>
	public sealed class CtmGenerator
	{
		private static YieldReaderWriterLockSlim _alreadyImplementedLock;
		private static readonly Dictionary<Type, Func<object>> _alreadyImplemented = new Dictionary<Type, Func<object>>();
		/// <summary>
		/// Implements and gets the constructor for a ICtmObject or CtmObject.
		/// </summary>
		public static Func<object> GetConstructor(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");
				
			var result = _alreadyImplemented.GetOrCreateValue(ref _alreadyImplementedLock, type, _GetConstructor);
			return result;
		}
		
		/// <summary>
		/// Implements and gets the constructor for the given abstract T type.
		/// </summary>
		public static Func<T> GetConstructor<T>()
		where
			T: class, ICtmObject
		{
			var untypedResult = GetConstructor(typeof(T));
			Func<T> result = () => (T)untypedResult();
			return result;
		}
		private static Func<object> _GetConstructor(Type type)
		{
			if (!type.IsAbstract)
				throw new ArgumentException("type must be abstract to be implemented.", "type");
				
			if (type.IsInterface)
			{
				if (type == typeof(ICtmObject) || !typeof(ICtmObject).IsAssignableFrom(type))
					throw new ArgumentException("type must be a sub-class of CtmObject or an interface that depends on ICtmObject.");
			}
			else
			{
				if (!type.IsSubclassOf(typeof(CtmObject)))
					throw new ArgumentException("type must be a sub-class of CtmObject or an interface that depends on ICtmObject.");
			}
				
			var generator = new CtmGenerator();
			var implementedType = generator._ImplementType(type);
			var result = ReflectionHelper.GetDefaultConstructorDelegate(implementedType);
			return result;
		}
		
		/// <summary>
		/// Creates an instance of the given ICtmObject or CtmObject type.
		/// </summary>
		public static object Create(Type type)
		{
			var constructor = GetConstructor(type);
			var result = constructor();
			return result;
		}
		
		/// <summary>
		/// Creates an instance of the given T abstract type.
		/// </summary>
		public static T Create<T>()
		where
			T: class, ICtmObject
		{
			var result = Create(typeof(T));
			return (T)result;
		}
	
		private static readonly ConstructorInfo _readOnlyExceptionConstructor = ReflectionHelper.GetConstructor(() => new ReadOnlyException());
		private static readonly MethodInfo _equalsMethod = ReflectionHelper<object>.GetMethod((o) => o.Equals(null));
		private static readonly Type[] _equalsParameters = new Type[]{typeof(object)};

		private static readonly ConstructorInfo _propertyChangedEventArgsConstructor = ReflectionHelper.GetConstructor(() => new PropertyChangedEventArgs("PropertyName"));
		private static readonly MethodInfo _propertyChangedEventHandlerInvoke = ReflectionHelper<PropertyChangedEventHandler>.GetMethod((o) => o.Invoke(null, null));
		
		private static readonly ConstructorInfo _ctmTypeConstructor = ReflectionHelper.GetConstructor(() => new CtmType(null, false));

		private static readonly MethodInfo _getTypeFromHandleMethod = ReflectionHelper.GetMethod(() => Type.GetTypeFromHandle(new RuntimeTypeHandle()));
		private static readonly MethodInfo _getIsReadOnlyMethod = ReflectionHelper<ICtmObject>.GetMethod((o) => o.GetIsReadOnly());
		private static readonly MethodInfo _getOldInstanceMethod = ReflectionHelper<ICtmObject>.GetMethod((o) => o.UntypedGetOldInstance());
		private static readonly MethodInfo _getAsReadOnlyMethod = ReflectionHelper<ICtmObject>.GetMethod((o) => o.UntypedAsReadOnly(true));
		private static readonly MethodInfo _getAsModifiableMethod = ReflectionHelper<ICtmObject>.GetMethod((o) => o.UntypedAsModifiable());
		private static readonly MethodInfo _copyValuesToMethod = ReflectionHelper<CtmObject>.GetMethod((o) => o.CopyValuesTo(null));
		
		private static readonly MethodInfo _copyValuesToMethodFromAutoInitProperty = ReflectionHelper<ICtmInternalCopyValues>.GetMethod((o) => o.CopyValuesTo(null));
		
		private static readonly EventInfo _propertyChangedEvent = typeof(INotifyPropertyChanged).GetEvent("PropertyChanged");
		private static readonly MethodInfo _addPropertyChangedHandlerMethod = typeof(CtmObject).GetMethod("AddPropertyChangedHandler", BindingFlags.NonPublic | BindingFlags.Instance);
		private static readonly MethodInfo _removePropertyChangedHandlerMethod = typeof(CtmObject).GetMethod("RemovePropertyChangedHandler", BindingFlags.NonPublic | BindingFlags.Instance);
		
		private ILGenerator _staticReadOnlyConstructorGenerator;
		private ILGenerator _staticModifiableConstructorGenerator;
		private Label _labelReturnFalse;
		private TypeBuilder _typeReadOnly;
		private ILGenerator _copyValuesToGenerator;
		private ILGenerator _equalsGenerator;
		private ReadOnlyCollection<PropertyInfo> _properties;
		private List<PropertyInfo> _modifiableProperties;
		private Dictionary<string, FieldBuilder> _modifiableArgs;
		private Dictionary<string, HashSet<string>> _dependencies;
		private Type _baseType;
		private TypeBuilder _typeModifiable;
		private FieldBuilder _propertyChangedField;
		private ILGenerator _readOnlyConstructorGenerator;
		private FieldBuilder _fieldOldInstance;
		private bool _implementingInterface;
		private bool _isTypeModifiable;

		private Type _ImplementType(Type typeToImplement)
		{
			_baseType = typeToImplement;
			Type[] interfaceTypes = Type.EmptyTypes;
			
			if (typeToImplement.IsInterface)
			{
				_implementingInterface = true;
				_baseType = typeof(CtmObject);
				_ChangeAbstractTypeIfNeeded(typeToImplement);
				foreach(var baseInterfaceType in typeToImplement.GetInterfaces())
					_ChangeAbstractTypeIfNeeded(baseInterfaceType);
				
				interfaceTypes = new Type[]{typeToImplement};
			}
		
			_isTypeModifiable = !typeToImplement.ContainsCustomAttribute<CtmNonUpdatableAttribute>();
			_typeReadOnly = _DynamicModule.DefineType(typeToImplement.FullName + "_ReadOnly", TypeAttributes.Class, _baseType, interfaceTypes); 
			_fieldOldInstance = _typeReadOnly.DefineField("`OldInstance", _typeReadOnly, FieldAttributes.Assembly);
			var readOnlyConstructor = _typeReadOnly.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, Type.EmptyTypes);
			_readOnlyConstructorGenerator = readOnlyConstructor.GetILGenerator();
			_readOnlyConstructorGenerator.Emit(OpCodes.Ldarg_0);
			_readOnlyConstructorGenerator.Emit(OpCodes.Call, _baseType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null));
			
			var staticReadOnlyConstructor = _typeReadOnly.DefineConstructor(MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
			_staticReadOnlyConstructorGenerator = staticReadOnlyConstructor.GetILGenerator();

			_typeModifiable = _DynamicModule.DefineType(typeToImplement.FullName + "_Modifiable", TypeAttributes.Sealed | TypeAttributes.Class, _typeReadOnly, Type.EmptyTypes);
			_propertyChangedField = _typeModifiable.DefineField("'PropertyChanged", typeof(PropertyChangedEventHandler), FieldAttributes.Private);

			var staticModifiableConstructor = _typeModifiable.DefineConstructor(MethodAttributes.Static, CallingConventions.Standard, Type.EmptyTypes);
			_staticModifiableConstructorGenerator = staticModifiableConstructor.GetILGenerator();

			_GenerateCtmType(typeToImplement);

			var modifiableConstructor = _typeModifiable.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, Type.EmptyTypes);
			var modifiableConstructorGenerator = modifiableConstructor.GetILGenerator();
			modifiableConstructorGenerator.Emit(OpCodes.Ldarg_0);
			modifiableConstructorGenerator.Emit(OpCodes.Call, readOnlyConstructor);
			modifiableConstructorGenerator.Emit(OpCodes.Ret);

			_dependencies = new Dictionary<string, HashSet<string>>();
			_properties = typeToImplement.GetInterfaceProperties();
			_GetModifiableProperties();
			var propertyTypes = _GetModifiablePropertyTypes();

			var copyValuesTo = _typeReadOnly.DefineMethod("`CopyValuesTo", MethodAttributes.FamANDAssem | MethodAttributes.Final, typeof(void), new Type[]{_typeReadOnly});
			_copyValuesToGenerator = copyValuesTo.GetILGenerator();
			var copyValuesOnBaseType = _baseType.GetMethod("CopyValuesTo", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[]{typeof(CtmObject)}, null);
			if (copyValuesOnBaseType.DeclaringType != typeof(CtmObject))
			{
				_copyValuesToGenerator.Emit(OpCodes.Ldarg_0);
				_copyValuesToGenerator.Emit(OpCodes.Ldarg_1);
				_copyValuesToGenerator.Emit(OpCodes.Call, copyValuesOnBaseType);
			}

			var equalsMethod = _typeReadOnly.DefineMethod("Equals", MethodAttributes.Public | MethodAttributes.Virtual, typeof(bool), _equalsParameters);
			var equalsOnBaseType = _baseType.GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance, null, new Type[]{typeof(object)}, null);
			_equalsGenerator = equalsMethod.GetILGenerator();
			_equalsGenerator.Emit(OpCodes.Ldarg_1);
			_equalsGenerator.Emit(OpCodes.Isinst, _typeReadOnly);
			_labelReturnFalse = _equalsGenerator.DefineLabel();
			_equalsGenerator.Emit(OpCodes.Brfalse, _labelReturnFalse);

			if (equalsOnBaseType.DeclaringType != typeof(object))
			{
				_equalsGenerator.Emit(OpCodes.Ldarg_0);
				_equalsGenerator.Emit(OpCodes.Ldarg_1);
				_equalsGenerator.Emit(OpCodes.Call, equalsOnBaseType);
				_equalsGenerator.Emit(OpCodes.Brfalse, _labelReturnFalse);
			}
			
			_MapDependencies();
			_GenerateProperties();

			_copyValuesToGenerator.Emit(OpCodes.Ret);

			_equalsGenerator.Emit(OpCodes.Ldc_I4_1);
			_equalsGenerator.Emit(OpCodes.Ret);
			_equalsGenerator.MarkLabel(_labelReturnFalse);
			_equalsGenerator.Emit(OpCodes.Ldc_I4_0);
			_equalsGenerator.Emit(OpCodes.Ret);
			_typeReadOnly.DefineMethodOverride(equalsMethod, equalsOnBaseType);

			// Read Only Type
			var addPropertyChangedOriginalMethod = _propertyChangedEvent.GetAddMethod();
			var addPropertyChangedMethod = _typeReadOnly.DefineMethod(addPropertyChangedOriginalMethod.Name, MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.Public, typeof(void), new Type[]{typeof(PropertyChangedEventHandler)});
			var addPropertyChangedGenerator = addPropertyChangedMethod.GetILGenerator();
			addPropertyChangedGenerator.Emit(OpCodes.Ret);
			_typeReadOnly.DefineMethodOverride(addPropertyChangedMethod, addPropertyChangedOriginalMethod);
			
			var removePropertyChangedOriginalMethod = _propertyChangedEvent.GetRemoveMethod();
			var removePropertyChangedMethod = _typeReadOnly.DefineMethod(removePropertyChangedOriginalMethod.Name, MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.Public, typeof(void), new Type[]{typeof(PropertyChangedEventHandler)});
			var removePropertyChangedGenerator = removePropertyChangedMethod.GetILGenerator();
			removePropertyChangedGenerator.Emit(OpCodes.Ret);
			_typeReadOnly.DefineMethodOverride(removePropertyChangedMethod, removePropertyChangedOriginalMethod);
			
			var getIsReadOnlyMethod = _typeReadOnly.DefineMethod(_getIsReadOnlyMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(bool), Type.EmptyTypes);
			var getIsReadOnlyGenerator= getIsReadOnlyMethod.GetILGenerator();
			getIsReadOnlyGenerator.Emit(OpCodes.Ldc_I4_1);
			getIsReadOnlyGenerator.Emit(OpCodes.Ret);
			_typeReadOnly.DefineMethodOverride(getIsReadOnlyMethod, _getIsReadOnlyMethod);

			var getOldInstanceMethod = _typeReadOnly.DefineMethod(_getOldInstanceMethod.Name, MethodAttributes.Private | MethodAttributes.Virtual, typeof(object), Type.EmptyTypes);
			var getOldInstanceGenerator = getOldInstanceMethod.GetILGenerator();
			getOldInstanceGenerator.Emit(OpCodes.Ldarg_0);
			getOldInstanceGenerator.Emit(OpCodes.Ldfld, _fieldOldInstance);
			getOldInstanceGenerator.Emit(OpCodes.Ret);
			_typeReadOnly.DefineMethodOverride(getOldInstanceMethod, _getOldInstanceMethod);

			var getAsReadOnlyMethod = _typeReadOnly.DefineMethod(_getAsReadOnlyMethod.Name, MethodAttributes.Private | MethodAttributes.Virtual, typeof(object), new Type[]{typeof(bool)});
			var getAsReadOnlyGenerator = getAsReadOnlyMethod.GetILGenerator();
			var labelReturnThis = getAsReadOnlyGenerator.DefineLabel();
			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_1);
			getAsReadOnlyGenerator.Emit(OpCodes.Brtrue_S, labelReturnThis);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_0);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldfld, _fieldOldInstance);
			getAsReadOnlyGenerator.Emit(OpCodes.Brfalse_S, labelReturnThis);
			
			// clone without keeping the source.
			getAsReadOnlyGenerator.Emit(OpCodes.Newobj, readOnlyConstructor);
			var localClone = getAsReadOnlyGenerator.DeclareLocal(_typeReadOnly);
			getAsReadOnlyGenerator.Emit(OpCodes.Stloc, localClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_0);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldloc, localClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Call, copyValuesTo);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldloc, localClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Ret);
			
			getAsReadOnlyGenerator.MarkLabel(labelReturnThis);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_0);
			getAsReadOnlyGenerator.Emit(OpCodes.Ret);
			_typeReadOnly.DefineMethodOverride(getAsReadOnlyMethod, _getAsReadOnlyMethod);

			var getAsModifiableMethod = _typeReadOnly.DefineMethod(_getAsModifiableMethod.Name, MethodAttributes.FamANDAssem | MethodAttributes.Virtual, typeof(object), Type.EmptyTypes);
			var getAsModifiableGenerator = getAsModifiableMethod.GetILGenerator();
			if (_isTypeModifiable)
			{
				getAsModifiableGenerator.Emit(OpCodes.Newobj, modifiableConstructor);
				var localClone1 = getAsModifiableGenerator.DeclareLocal(_typeModifiable);
				getAsModifiableGenerator.Emit(OpCodes.Stloc, localClone1);

				getAsModifiableGenerator.Emit(OpCodes.Ldloc, localClone1);
				getAsModifiableGenerator.Emit(OpCodes.Ldarg_0);
				getAsModifiableGenerator.Emit(OpCodes.Stfld, _fieldOldInstance);

				getAsModifiableGenerator.Emit(OpCodes.Ldarg_0);
				getAsModifiableGenerator.Emit(OpCodes.Ldloc, localClone1);
				getAsModifiableGenerator.Emit(OpCodes.Call, copyValuesTo);

				getAsModifiableGenerator.Emit(OpCodes.Ldloc, localClone1);
				getAsModifiableGenerator.Emit(OpCodes.Ret);
			}
			else
			{
				getAsModifiableGenerator.Emit(OpCodes.Newobj, _readOnlyExceptionConstructor);
				getAsModifiableGenerator.Emit(OpCodes.Throw);
			}
			_typeReadOnly.DefineMethodOverride(getAsModifiableMethod, _getAsModifiableMethod);

			// Modifiable Type
			addPropertyChangedMethod = _typeModifiable.DefineMethod(addPropertyChangedOriginalMethod.Name, MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.Public, typeof(void), new Type[]{typeof(PropertyChangedEventHandler)});
			addPropertyChangedGenerator = addPropertyChangedMethod.GetILGenerator();
			addPropertyChangedGenerator.Emit(OpCodes.Ldarg_0);
			addPropertyChangedGenerator.Emit(OpCodes.Ldarg_0);
			addPropertyChangedGenerator.Emit(OpCodes.Ldflda, _propertyChangedField);
			addPropertyChangedGenerator.Emit(OpCodes.Ldarg_1);
			addPropertyChangedGenerator.Emit(OpCodes.Call, _addPropertyChangedHandlerMethod);
			addPropertyChangedGenerator.Emit(OpCodes.Ret);
			_typeModifiable.DefineMethodOverride(addPropertyChangedMethod, addPropertyChangedOriginalMethod);
			
			removePropertyChangedMethod = _typeModifiable.DefineMethod(removePropertyChangedOriginalMethod.Name, MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.Public, typeof(void), new Type[]{typeof(PropertyChangedEventHandler)});
			removePropertyChangedGenerator = removePropertyChangedMethod.GetILGenerator();
			removePropertyChangedGenerator.Emit(OpCodes.Ldarg_0);
			removePropertyChangedGenerator.Emit(OpCodes.Ldarg_0);
			removePropertyChangedGenerator.Emit(OpCodes.Ldflda, _propertyChangedField);
			removePropertyChangedGenerator.Emit(OpCodes.Ldarg_1);
			removePropertyChangedGenerator.Emit(OpCodes.Call, _removePropertyChangedHandlerMethod);
			removePropertyChangedGenerator.Emit(OpCodes.Ret);
			_typeModifiable.DefineMethodOverride(removePropertyChangedMethod, removePropertyChangedOriginalMethod);

			getIsReadOnlyMethod = _typeModifiable.DefineMethod(_getIsReadOnlyMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, typeof(bool), Type.EmptyTypes);
			getIsReadOnlyGenerator= getIsReadOnlyMethod.GetILGenerator();
			getIsReadOnlyGenerator.Emit(OpCodes.Ldc_I4_0);
			getIsReadOnlyGenerator.Emit(OpCodes.Ret);
			_typeModifiable.DefineMethodOverride(getIsReadOnlyMethod, _getIsReadOnlyMethod);

			getAsReadOnlyMethod = _typeModifiable.DefineMethod(_getAsReadOnlyMethod.Name, MethodAttributes.Private | MethodAttributes.Virtual, typeof(object), new Type[]{typeof(bool)});
			getAsReadOnlyGenerator = getAsReadOnlyMethod.GetILGenerator();
			localClone = getAsReadOnlyGenerator.DeclareLocal(_typeReadOnly);
			getAsReadOnlyGenerator.Emit(OpCodes.Newobj, readOnlyConstructor);
			getAsReadOnlyGenerator.Emit(OpCodes.Stloc, localClone);

			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_0);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldloc, localClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Call, copyValuesTo);
			
			var labelReturnClone = getAsReadOnlyGenerator.DefineLabel();
			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_1);
			getAsReadOnlyGenerator.Emit(OpCodes.Brfalse_S, labelReturnClone);

			getAsReadOnlyGenerator.Emit(OpCodes.Ldloc, localClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldarg_0);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldfld, _fieldOldInstance);
			getAsReadOnlyGenerator.Emit(OpCodes.Stfld, _fieldOldInstance);
			
			getAsReadOnlyGenerator.MarkLabel(labelReturnClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Ldloc, localClone);
			getAsReadOnlyGenerator.Emit(OpCodes.Ret);
			_typeModifiable.DefineMethodOverride(getAsReadOnlyMethod, _getAsReadOnlyMethod);

			getAsModifiableMethod = _typeModifiable.DefineMethod(_getAsModifiableMethod.Name, MethodAttributes.FamANDAssem | MethodAttributes.Virtual, typeof(object), Type.EmptyTypes);
			getAsModifiableGenerator = getAsModifiableMethod.GetILGenerator();
			getAsModifiableGenerator.Emit(OpCodes.Ldarg_0);
			getAsModifiableGenerator.Emit(OpCodes.Ret);
			_typeModifiable.DefineMethodOverride(getAsModifiableMethod, _getAsModifiableMethod);

			_staticReadOnlyConstructorGenerator.Emit(OpCodes.Ret);
			_staticModifiableConstructorGenerator.Emit(OpCodes.Ret);
			_readOnlyConstructorGenerator.Emit(OpCodes.Ret);

			_typeReadOnly.CreateType();
			return _typeModifiable.CreateType();
		}

		private void _GenerateCtmType(Type abstractType)
		{
			var field = _typeReadOnly.DefineField(".ctmType", typeof(CtmType), FieldAttributes.Static | FieldAttributes.InitOnly | FieldAttributes.Private);
			_staticReadOnlyConstructorGenerator.Emit(OpCodes.Ldtoken, abstractType);
			_staticReadOnlyConstructorGenerator.Emit(OpCodes.Call, _getTypeFromHandleMethod);
			
			int nonUpdatable = 0;
			if (!_isTypeModifiable)
				nonUpdatable = 1;
				
			_staticReadOnlyConstructorGenerator.Emit(OpCodes.Ldc_I4, nonUpdatable);
			_staticReadOnlyConstructorGenerator.Emit(OpCodes.Newobj, _ctmTypeConstructor);
			_staticReadOnlyConstructorGenerator.Emit(OpCodes.Stsfld, field);
			
			var getCtmTypeMethod = _typeReadOnly.DefineMethod("GetCtmType", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual, typeof(CtmType), Type.EmptyTypes);
			var getCtmTypeGenerator = getCtmTypeMethod.GetILGenerator();
			getCtmTypeGenerator.Emit(OpCodes.Ldsfld, field);
			getCtmTypeGenerator.Emit(OpCodes.Ret);
		}

		private void _ChangeAbstractTypeIfNeeded(Type typeToImplement)
		{
			var baseClassAttribute = typeToImplement.GetCustomAttribute<CtmBaseClassAttribute>();
			if (baseClassAttribute == null)
				return;
				
			var otherBaseClass = baseClassAttribute.Type;
			if (otherBaseClass == _baseType)
				return;
				
			if (otherBaseClass.IsSubclassOf(_baseType))
			{
				_baseType = otherBaseClass;
				return;
			}
			
			if (!_baseType.IsSubclassOf(otherBaseClass))
				throw new ArgumentException("The given interface has conflicting BaseTypes. Check the [BaseClassAttribute] for all involved interfaces.");
		}

		private void _MapDependencies()
		{
			bool hasNewDependencies = false;
			foreach(var calculatedProperty in _properties)
			{
				var realProperty = calculatedProperty;
				
				string propertyName = calculatedProperty.Name;
				if (calculatedProperty.DeclaringType.IsInterface)
				{
					realProperty = _baseType.GetProperty(propertyName);
					if (realProperty == null)
						continue;
				}

				var attribute = realProperty.GetCustomAttribute<CtmCalculatedPropertyAttribute>();
				if (attribute == null)
					continue;

				hasNewDependencies = true;

				foreach(var sourceName in attribute.PropertyNames)
				{
					var sourceProperty = _baseType.GetProperty(sourceName);
					if (sourceProperty == null)
						throw new InvalidOperationException("Property " + propertyName + " depends on property " + sourceName + " which was not found.");

					var listDependents = _dependencies.GetOrCreateValue(sourceName);
					listDependents.Add(calculatedProperty.Name);
				}
			}

			while(hasNewDependencies)
			{
				hasNewDependencies = false;

				foreach(var pair in _dependencies)
				{
					var sourceProperty = pair.Key;
					var hash = pair.Value;

					foreach(var dependentProperty in hash.ToArray())
					{
						var otherList = _dependencies.GetValueOrDefault(dependentProperty);
						if (otherList == null)
							continue;

						foreach(var otherDependency in otherList)
						{
							if (otherDependency == sourceProperty)
								continue;

							if (hash.Add(otherDependency))
								hasNewDependencies = true;
						}
					}
				}
			}

			// At this moment, the _dependencies has all direct and indirect dependencies
			// of changeable properties. The non-changeable but calculated are still in the
			// dictionary, but it is useless to remove them from the dictionary, as the dictionary
			// will die soon.
		}

		private void _GetModifiableProperties()
		{
			_modifiableArgs = new Dictionary<string, FieldBuilder>();
			_modifiableProperties = new List<PropertyInfo>();

			foreach(var property in _properties)
			{
				var getMethod = property.GetGetMethod();
				if (getMethod == null)
					continue;
					
				if (!getMethod.IsAbstract)
					continue;

				// TODO must do a better checking. After all, the property can be
				// read-only in the interface, and should be implemented redirecting
				// to the base class.
				//var setMethod = property.GetSetMethod();
				//if (setMethod == null)
				//	continue;

				//if (!getMethod.IsAbstract || getMethod.IsStatic)
				//	continue;

				_modifiableProperties.Add(property);
			}
		}

		private Type[] _GetModifiablePropertyTypes()
		{
			int count = _modifiableProperties.Count;
			var result = new Type[count];
			for(int i=0; i<count; i++)
			{
				var property = _modifiableProperties[i];
				result[i] = property.PropertyType;
			}

			return result;
		}

		private void _GenerateProperties()
		{
			int parameterIndex = 0;
			foreach(var propertyToImplement in _modifiableProperties)
			{
				parameterIndex++;

				string name = propertyToImplement.Name;
				if (_implementingInterface)
				{
					var propertyInBase = _baseType.GetProperty(name);
					if (propertyInBase != null)
					{
						var baseGetMethod = propertyInBase.GetGetMethod();
						if (baseGetMethod == null)
							throw new InvalidOperationException("Can't get the Get method for property: " + name);
							
						if (!baseGetMethod.IsAbstract)
						{
							_RedirectToBase(propertyToImplement, propertyInBase);
							continue;
						}
					}
				}

				var getMethod = propertyToImplement.GetGetMethod();
				var setMethod = propertyToImplement.GetSetMethod();

				Type propertyType = propertyToImplement.PropertyType;
				var field = _typeReadOnly.DefineField("_" + name, propertyType, FieldAttributes.FamANDAssem);
				var implementedGetMethod = _typeReadOnly.DefineMethod(getMethod.Name, MethodAttributes.SpecialName | MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, propertyType, Type.EmptyTypes);
				var getMethodGenerator = implementedGetMethod.GetILGenerator();
				getMethodGenerator.Emit(OpCodes.Ldarg_0);
				getMethodGenerator.Emit(OpCodes.Ldfld, field);
				getMethodGenerator.Emit(OpCodes.Ret);
				_typeReadOnly.DefineMethodOverride(implementedGetMethod, getMethod);

				bool isAutoInitializeProperty = propertyType.IsSubclassOf(typeof(CtmAutoInitialize));
				if (isAutoInitializeProperty)
				{
					_copyValuesToGenerator.Emit(OpCodes.Ldarg_0);
					_copyValuesToGenerator.Emit(OpCodes.Ldfld, field);
					_copyValuesToGenerator.Emit(OpCodes.Ldarg_1);
					_copyValuesToGenerator.Emit(OpCodes.Ldfld, field);
					_copyValuesToGenerator.Emit(OpCodes.Callvirt, _copyValuesToMethodFromAutoInitProperty);
				}
				else
				{
					_copyValuesToGenerator.Emit(OpCodes.Ldarg_1);
					_copyValuesToGenerator.Emit(OpCodes.Ldarg_0);
					_copyValuesToGenerator.Emit(OpCodes.Ldfld, field);
					_copyValuesToGenerator.Emit(OpCodes.Stfld, field);
				}

				var equalityComparerType = typeof(EqualityComparer<>);
				equalityComparerType = equalityComparerType.MakeGenericType(propertyType);
				var defaultEqualityComparer = equalityComparerType.GetProperty("Default");
				_equalsGenerator.Emit(OpCodes.Call, defaultEqualityComparer.GetGetMethod());

				_equalsGenerator.Emit(OpCodes.Ldarg_0);
				_equalsGenerator.Emit(OpCodes.Ldfld, field);
				_equalsGenerator.Emit(OpCodes.Ldarg_1);
				_equalsGenerator.Emit(OpCodes.Ldfld, field);

				var equalsWith2Parameters = equalityComparerType.GetMethod("Equals", new Type[]{propertyType, propertyType});
				_equalsGenerator.Emit(OpCodes.Callvirt, equalsWith2Parameters); 
				_equalsGenerator.Emit(OpCodes.Brfalse, _labelReturnFalse);

				// Modifiable properties
				if (setMethod != null)
				{
					if (isAutoInitializeProperty)
						throw new InvalidOperationException("Properties of types derived from AutoInitializeCloneToModifyProperty can't have setters. Property: " + name);
						
					var implementedSetMethod = _typeReadOnly.DefineMethod(setMethod.Name, MethodAttributes.SpecialName | MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new Type[]{propertyType});
					var setMethodGenerator = implementedSetMethod.GetILGenerator();
					setMethodGenerator.Emit(OpCodes.Newobj, _readOnlyExceptionConstructor);
					setMethodGenerator.Emit(OpCodes.Throw);
					_typeReadOnly.DefineMethodOverride(implementedSetMethod, setMethod);

					var modifiableSet = _typeModifiable.DefineMethod(setMethod.Name, MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.Public, typeof(void), new Type[]{propertyType});
					setMethodGenerator = modifiableSet.GetILGenerator();

					bool isPropertyModifiable = _isTypeModifiable;
					if (isPropertyModifiable)
						isPropertyModifiable = !propertyToImplement.ContainsCustomAttribute<CtmNonUpdatableAttribute>();

					if (!isPropertyModifiable)
					{
						var labelOk = setMethodGenerator.DefineLabel();
						
						setMethodGenerator.Emit(OpCodes.Ldarg_0);
						setMethodGenerator.Emit(OpCodes.Ldfld, _fieldOldInstance);
						setMethodGenerator.Emit(OpCodes.Brfalse_S, labelOk);

						setMethodGenerator.Emit(OpCodes.Newobj, _readOnlyExceptionConstructor);
						setMethodGenerator.Emit(OpCodes.Throw);
						
						setMethodGenerator.MarkLabel(labelOk);
					}


					var labelReturn = setMethodGenerator.DefineLabel();
					
					setMethodGenerator.Emit(OpCodes.Call, defaultEqualityComparer.GetGetMethod());

					setMethodGenerator.Emit(OpCodes.Ldarg_0);
					setMethodGenerator.Emit(OpCodes.Ldfld, field);
					setMethodGenerator.Emit(OpCodes.Ldarg_1);

					setMethodGenerator.Emit(OpCodes.Callvirt, equalsWith2Parameters); 
					setMethodGenerator.Emit(OpCodes.Brtrue, labelReturn);

					// Then set the value.
					setMethodGenerator.Emit(OpCodes.Ldarg_0);
					setMethodGenerator.Emit(OpCodes.Ldarg_1);
					setMethodGenerator.Emit(OpCodes.Stfld, field);

					setMethodGenerator.Emit(OpCodes.Ldarg_0);
					setMethodGenerator.Emit(OpCodes.Ldfld, _propertyChangedField);
					var localHandler = setMethodGenerator.DeclareLocal(typeof(PropertyChangedEventHandler));
					setMethodGenerator.Emit(OpCodes.Stloc, localHandler);

					setMethodGenerator.Emit(OpCodes.Ldloc, localHandler);
					setMethodGenerator.Emit(OpCodes.Brfalse, labelReturn);

					_GenerateNotification(propertyToImplement.Name, setMethodGenerator, localHandler);
					HashSet<string> dependentProperties;
					if (_dependencies.TryGetValue(propertyToImplement.Name, out dependentProperties))
						foreach(var dependentProperty in dependentProperties)
							_GenerateNotification(dependentProperty, setMethodGenerator, localHandler);

					setMethodGenerator.MarkLabel(labelReturn);
					setMethodGenerator.Emit(OpCodes.Ret);
					_typeModifiable.DefineMethodOverride(modifiableSet, setMethod);
				}
				else
				{
					if (!isAutoInitializeProperty)
						throw new InvalidOperationException("Auto-implemented get only properties must be of sub-types of AutoInitializeCloneToModifyProperty.");
						
					var constructors = propertyType.GetConstructors();
					if (constructors.Length == 0)
						throw new InvalidOperationException("Type " + propertyType.FullName + " does not has public constructors.");
						
					ConstructorInfo constructor = null;
					foreach(var possibleConstructor in constructors)
					{
						var parameters = possibleConstructor.GetParameters();
						if (parameters.Length != 2)
							continue;
							
						var parameter = parameters[0];
						var parameterType = parameter.ParameterType;
						if (!typeof(ICtmObject).IsAssignableFrom(parameterType))
							continue;

						var parameter2 = parameters[1];
						if (parameter2.ParameterType != typeof(bool))
							continue;
							
						if (constructor != null)
							throw new InvalidOperationException("Type " + propertyType.FullName + " has more than one valid constructor.");
							
						if (!parameterType.IsAssignableFrom(_baseType))
							continue;
							
						constructor = possibleConstructor;
					}
					
					if (constructor == null)
						throw new InvalidOperationException("Type " + propertyType.FullName + " does not has a valid constructor.");

					int isNonUpdatable = 0;
					if (propertyToImplement.ContainsCustomAttribute<CtmNonUpdatableAttribute>())
						isNonUpdatable = 1;
						
					_readOnlyConstructorGenerator.Emit(OpCodes.Ldarg_0);
					_readOnlyConstructorGenerator.Emit(OpCodes.Ldarg_0);
					_readOnlyConstructorGenerator.Emit(OpCodes.Ldc_I4, isNonUpdatable);
					_readOnlyConstructorGenerator.Emit(OpCodes.Newobj, constructor);
					_readOnlyConstructorGenerator.Emit(OpCodes.Stfld, field);
				}
			}
		}

		private void _RedirectToBase(PropertyInfo propertyToImplement, PropertyInfo propertyInBase)
		{
			string name = propertyToImplement.Name;
			Type propertyType = propertyToImplement.PropertyType;
			if (propertyInBase.PropertyType != propertyType)
				throw new InvalidOperationException("The property " + name + " has a different type in the interface and in the base class.");
				
			if (propertyToImplement.CanRead)
			{
				var getMethodInBase = propertyInBase.GetGetMethod();
				if (getMethodInBase == null)
					throw new InvalidOperationException("The property " + name + " does not has an acessible get method in the base class.");
					
				var getMethod = _typeReadOnly.DefineMethod("." + getMethodInBase.Name, MethodAttributes.Private | MethodAttributes.Virtual, propertyType, Type.EmptyTypes);
				var getGenerator = getMethod.GetILGenerator();
				getGenerator.Emit(OpCodes.Ldarg_0);
				getGenerator.Emit(OpCodes.Call, getMethodInBase);
				getGenerator.Emit(OpCodes.Ret);
				_typeReadOnly.DefineMethodOverride(getMethod, propertyToImplement.GetGetMethod());
			}
			
			if (propertyToImplement.CanWrite)
			{
				var setMethodInBase = propertyInBase.GetSetMethod();
				if (setMethodInBase == null)
					throw new InvalidOperationException("The property " + name + " does not has an acessible set method in the base class.");
					
				var setMethod = _typeReadOnly.DefineMethod("." + setMethodInBase.Name, MethodAttributes.Private | MethodAttributes.Virtual, typeof(void), new Type[]{propertyType});
				var setGenerator = setMethod.GetILGenerator();
				setGenerator.Emit(OpCodes.Ldarg_0);
				setGenerator.Emit(OpCodes.Ldarg_1);
				setGenerator.Emit(OpCodes.Call, setMethodInBase);
				setGenerator.Emit(OpCodes.Ret);
				_typeReadOnly.DefineMethodOverride(setMethod, propertyToImplement.GetSetMethod());
			}
		}

		private void _GenerateNotification(string propertyName, ILGenerator setMethodGenerator, LocalBuilder localHandler)
		{
			FieldBuilder argsField;
			if (!_modifiableArgs.TryGetValue(propertyName, out argsField))
			{
				argsField = _typeModifiable.DefineField("`Args." + propertyName, typeof(PropertyChangedEventArgs), FieldAttributes.Static | FieldAttributes.InitOnly | FieldAttributes.Private);
				_modifiableArgs.Add(propertyName, argsField);

				_staticModifiableConstructorGenerator.Emit(OpCodes.Ldstr, propertyName);
				_staticModifiableConstructorGenerator.Emit(OpCodes.Newobj, _propertyChangedEventArgsConstructor);
				_staticModifiableConstructorGenerator.Emit(OpCodes.Stsfld, argsField);
			}

			setMethodGenerator.Emit(OpCodes.Ldloc, localHandler);
			setMethodGenerator.Emit(OpCodes.Ldarg_0);
			setMethodGenerator.Emit(OpCodes.Ldsfld, argsField);
			setMethodGenerator.Emit(OpCodes.Callvirt, _propertyChangedEventHandlerInvoke);
		}

	}
}
